﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

namespace CMS6Tools.Common.IdentityImpersonate
{
    public class IdentityImpersonation
    {
        #region Const
        public const int LOGON32_LOGON_INTERACTIVE = 2;
        public const int LOGON32_PROVIDER_DEFAULT = 0;
        #endregion

        #region Static ReadOnly
        public static readonly IdentityImpersonation None = new IdentityImpersonation(false);
        #endregion

        #region Ctor
        public IdentityImpersonation(ImpersonatedIdentity identity)
        {
            Identity = identity;
            _needImpersonate = true;
        }
        public IdentityImpersonation(string domain, string userName, string password)
            : this(new ImpersonatedIdentity
            {
                Domain = domain,
                UserName = userName,
                Password = password
            })
        {
        }
        private IdentityImpersonation(bool needImpersonate)
        {
            _needImpersonate = needImpersonate;
        }
        #endregion

        #region Properties
        bool _needImpersonate = true;
        WindowsImpersonationContext _impersonationContext;
        public ImpersonatedIdentity Identity { get; private set; }
        #endregion

        #region Win32 APIs
        [DllImport("advapi32.dll")]
        public static extern int LogonUserA(String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool RevertToSelf();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool CloseHandle(IntPtr handle);
        #endregion

        #region Impersonate && UnImpersonate
        public bool Impersonate()
        {
            if (!_needImpersonate)
                return true;

            WindowsIdentity tempWindowsIdentity;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;


            if (RevertToSelf())
            {
                if (LogonUserA(Identity.UserName, Identity.Domain, Identity.Password, LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        _impersonationContext = tempWindowsIdentity.Impersonate();
                        if (_impersonationContext != null)
                        {
                            CloseHandle(token);
                            CloseHandle(tokenDuplicate);
                            return true;
                        }
                    }
                }
            }
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (tokenDuplicate != IntPtr.Zero)
                CloseHandle(tokenDuplicate);
            return false;
        }

        public void UnImpersonate()
        {
            if (_needImpersonate && _impersonationContext != null)
                _impersonationContext.Undo();
        }
        #endregion

        #region ExecuteCodeUnderImpersonation
        public bool ExecuteCodeUnderImpersonation(Action action)
        {
            if (Impersonate())
            {
                action();
                UnImpersonate();
                return true;
            }
            return false;
        }
        #endregion
    }

    public class ImpersonatedIdentity
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Domain { get; set; }
    }
}
